a11y: Rework ownership and lifetime of GtkATContext
authorEmmanuele Bassi <ebassi@gnome.org>
Wed, 11 Nov 2020 18:17:41 +0000 (18:17 +0000)
committerEmmanuele Bassi <ebassi@gnome.org>
Wed, 11 Nov 2020 19:45:43 +0000 (19:45 +0000)
Now that GtkATContext is explicitly realized and unrealized, we should
always create an instance at widget initialization time, and drop it
during the widget finalization. This should make it easier to set up the
initial accessible state of a widget during the instance initialization,
as well as reduce the chances of accidental creation of GtkATContext
instances during the destruction sequence.

gtk/gtkatcontext.c
gtk/gtkatcontextprivate.h
gtk/gtkwidget.c

index 74f368d5e7266926490db633447e0fd10407e065..8c49c27118bf64aac82b458f492b59197bbaedcc 100644 (file)
@@ -111,7 +111,7 @@ gtk_at_context_set_property (GObject      *gobject,
       break;
 
     case PROP_DISPLAY:
-      self->display = g_value_get_object (value);
+      gtk_at_context_set_display (self, g_value_get_object (value));
       break;
 
     default:
@@ -245,8 +245,8 @@ gtk_at_context_class_init (GtkATContextClass *klass)
                          "The display connection",
                          GDK_TYPE_DISPLAY,
                          G_PARAM_READWRITE |
-                         G_PARAM_CONSTRUCT_ONLY |
-                         G_PARAM_STATIC_STRINGS);
+                         G_PARAM_STATIC_STRINGS |
+                         G_PARAM_EXPLICIT_NOTIFY);
 
   /**
    * GtkATContext::state-change:
@@ -385,15 +385,15 @@ gtk_at_context_init (GtkATContext *self)
   self->accessible_role = GTK_ACCESSIBLE_ROLE_NONE;
 
   self->properties =
-    gtk_accessible_attribute_set_new (N_PROPERTIES,
+    gtk_accessible_attribute_set_new (G_N_ELEMENTS (property_attrs),
                                       property_attrs,
                                       (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_property);
   self->relations =
-    gtk_accessible_attribute_set_new (N_RELATIONS,
+    gtk_accessible_attribute_set_new (G_N_ELEMENTS (relation_attrs),
                                       relation_attrs,
                                       (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_relation);
   self->states =
-    gtk_accessible_attribute_set_new (N_STATES,
+    gtk_accessible_attribute_set_new (G_N_ELEMENTS (state_attrs),
                                       state_attrs,
                                       (GtkAccessibleAttributeDefaultFunc) gtk_accessible_value_get_default_for_state);
 }
@@ -414,6 +414,30 @@ gtk_at_context_get_accessible (GtkATContext *self)
   return self->accessible;
 }
 
+/*< private >
+ * gtk_at_context_set_accessible_role:
+ * @self: a #GtkATContext
+ * @role: the accessible role for the context
+ *
+ * Sets the accessible role for the given #GtkATContext.
+ *
+ * This function can only be called if the #GtkATContext is unrealized.
+ */
+void
+gtk_at_context_set_accessible_role (GtkATContext      *self,
+                                    GtkAccessibleRole  role)
+{
+  g_return_if_fail (GTK_IS_AT_CONTEXT (self));
+  g_return_if_fail (!self->realized);
+
+  if (self->accessible_role == role)
+    return;
+
+  self->accessible_role = role;
+
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ACCESSIBLE_ROLE]);
+}
+
 /**
  * gtk_at_context_get_accessible_role:
  * @self: a #GtkATContext
@@ -430,6 +454,34 @@ gtk_at_context_get_accessible_role (GtkATContext *self)
   return self->accessible_role;
 }
 
+/*< private >
+ * gtk_at_context_set_display:
+ * @self: a #GtkATContext
+ * @display: a #GdkDisplay
+ *
+ * Sets the #GdkDisplay used by the #GtkATContext.
+ *
+ * This function can only be called if the #GtkATContext is
+ * not realized.
+ */
+void
+gtk_at_context_set_display (GtkATContext *self,
+                            GdkDisplay   *display)
+{
+  g_return_if_fail (GTK_IS_AT_CONTEXT (self));
+  g_return_if_fail (display == NULL || GDK_IS_DISPLAY (display));
+
+  if (self->display == display)
+    return;
+
+  if (self->realized)
+    return;
+
+  self->display = display;
+
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_DISPLAY]);
+}
+
 /*< private >
  * gtk_at_context_get_display:
  * @self: a #GtkATContext
index f1b62f8cb82b4314cd12ae1f869d8be65f3ac58d..d9678cbea56d0c0c1162196862089e0ab3ddf410 100644 (file)
@@ -150,7 +150,11 @@ GtkATContext *          gtk_at_context_clone                    (GtkATContext
                                                                  GtkAccessible         *accessible,
                                                                  GdkDisplay            *display);
 
+void                    gtk_at_context_set_display              (GtkATContext          *self,
+                                                                 GdkDisplay            *display);
 GdkDisplay *            gtk_at_context_get_display              (GtkATContext          *self);
+void                    gtk_at_context_set_accessible_role      (GtkATContext          *self,
+                                                                 GtkAccessibleRole      role);
 
 void                    gtk_at_context_realize                  (GtkATContext          *self);
 void                    gtk_at_context_unrealize                (GtkATContext          *self);
index 48a394a766a347633340af36003440263f372e46..7c848040e3d2be504e3c47475c3b613b2d5a84ff 100644 (file)
@@ -771,8 +771,6 @@ gtk_widget_base_class_init (gpointer g_class)
           g_object_unref (shortcut);
         }
     }
-
-  priv->accessible_role = GTK_ACCESSIBLE_ROLE_WIDGET;
 }
 
 static void
@@ -1624,6 +1622,7 @@ gtk_widget_class_init (GtkWidgetClass *klass)
                               _gtk_marshal_BOOLEAN__INT_INT_BOOLEAN_OBJECTv);
 
   gtk_widget_class_set_css_name (klass, I_("widget"));
+  gtk_widget_class_set_accessible_role (klass, GTK_ACCESSIBLE_ROLE_WIDGET);
 }
 
 static void
@@ -2300,6 +2299,34 @@ gtk_widget_init (GTypeInstance *instance, gpointer g_class)
       gtk_event_controller_set_name (controller, "gtk-widget-class-shortcuts");
       gtk_widget_add_controller (widget, controller);
     }
+
+  priv->at_context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (widget));
+}
+
+static void
+gtk_widget_realize_at_context (GtkWidget *self)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (self);
+  GtkAccessibleRole role = priv->accessible_role;
+
+  if (gtk_at_context_is_realized (priv->at_context))
+    return;
+
+  /* Realize the root ATContext first */
+  if (!GTK_IS_ROOT (self))
+    gtk_widget_realize_at_context (GTK_WIDGET (priv->root));
+
+  /* Reset the accessible role to its current value */
+  if (role == GTK_ACCESSIBLE_ROLE_WIDGET)
+    {
+      GtkWidgetClassPrivate *class_priv = GTK_WIDGET_GET_CLASS (self)->priv;
+
+      role = class_priv->accessible_role;
+    }
+
+  gtk_at_context_set_accessible_role (priv->at_context, role);
+  gtk_at_context_set_display (priv->at_context, gtk_root_get_display (priv->root));
+  gtk_at_context_realize (priv->at_context);
 }
 
 void
@@ -2330,15 +2357,7 @@ gtk_widget_root (GtkWidget *widget)
   if (priv->layout_manager)
     gtk_layout_manager_set_root (priv->layout_manager, priv->root);
 
-  if (priv->at_context != NULL)
-    {
-      GtkATContext *root_context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (priv->root));
-
-      if (root_context)
-        gtk_at_context_realize (root_context);
-
-      gtk_at_context_realize (priv->at_context);
-    }
+  gtk_widget_realize_at_context (widget);
 
   GTK_WIDGET_GET_CLASS (widget)->root (widget);
 
@@ -2364,8 +2383,8 @@ gtk_widget_unroot (GtkWidget *widget)
 
   GTK_WIDGET_GET_CLASS (widget)->unroot (widget);
 
-  if (priv->at_context)
-    gtk_at_context_unrealize (priv->at_context);
+  gtk_at_context_set_display (priv->at_context, gdk_display_get_default ());
+  gtk_at_context_unrealize (priv->at_context);
 
   if (priv->context)
     gtk_style_context_set_display (priv->context, gdk_display_get_default ());
@@ -8109,33 +8128,36 @@ gtk_widget_accessible_get_at_context (GtkAccessible *accessible)
 {
   GtkWidget *self = GTK_WIDGET (accessible);
   GtkWidgetPrivate *priv = gtk_widget_get_instance_private (self);
+  GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS (self);
+  GtkWidgetClassPrivate *class_priv = widget_class->priv;
+  GtkAccessibleRole role;
 
   if (priv->in_destruction)
-    return NULL;
-
-  if (priv->at_context == NULL)
     {
-      GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS (self);
-      GtkWidgetClassPrivate *class_priv = widget_class->priv;
-      GdkDisplay *display = _gtk_widget_get_display (self);
-      GtkAccessibleRole role;
+      GTK_NOTE (A11Y, g_message ("ATContext for widget ā€œ%sā€ [%p] accessed during destruction",
+                                 G_OBJECT_TYPE_NAME (self),
+                                 self));
+      return NULL;
+    }
 
-      /* Widgets have two options to set the accessible role: either they
-       * define it in their class_init() function, and the role applies to
-       * all instances; or an instance is created with the :accessible-role
-       * property (from GtkAccessible) set to anything other than the default
-       * GTK_ACCESSIBLE_ROLE_WIDGET value.
-       *
-       * In either case, the accessible role cannot be set post-construction.
-       */
-      if (priv->accessible_role != GTK_ACCESSIBLE_ROLE_WIDGET)
-        role = priv->accessible_role;
-      else
-        role = class_priv->accessible_role;
+  if (priv->at_context != NULL)
+    return priv->at_context;
 
-      priv->accessible_role = role;
-      priv->at_context = gtk_at_context_create (role, accessible, display);
-    }
+  /* Widgets have two options to set the accessible role: either they
+   * define it in their class_init() function, and the role applies to
+   * all instances; or an instance is created with the :accessible-role
+   * property (from GtkAccessible) set to anything other than the default
+   * GTK_ACCESSIBLE_ROLE_WIDGET value.
+   *
+   * In either case, the accessible role cannot be set post-construction.
+   */
+  if (priv->accessible_role != GTK_ACCESSIBLE_ROLE_WIDGET)
+    role = priv->accessible_role;
+  else
+    role = class_priv->accessible_role;
+
+  priv->accessible_role = role;
+  priv->at_context = gtk_at_context_create (role, accessible, gdk_display_get_default ());
 
   return priv->at_context;
 }
@@ -12565,7 +12587,7 @@ gtk_widget_set_accessible_role (GtkWidget         *self,
       priv->accessible_role = role;
 
       if (priv->at_context != NULL)
-        g_object_set (priv->at_context, "accessible-role", priv->accessible_role, NULL);
+        gtk_at_context_set_accessible_role (priv->at_context, role);
 
       g_object_notify (G_OBJECT (self), "accessible-role");
     }
@@ -12586,15 +12608,10 @@ gtk_widget_get_accessible_role (GtkWidget *self)
   GtkWidgetPrivate *priv = gtk_widget_get_instance_private (self);
   GtkATContext *context = gtk_accessible_get_at_context (GTK_ACCESSIBLE (self));
 
-  if (context == NULL || !gtk_at_context_is_realized (context))
-    {
-      if (priv->accessible_role == GTK_ACCESSIBLE_ROLE_WIDGET)
-        return gtk_widget_class_get_accessible_role (GTK_WIDGET_GET_CLASS (self));
-
-      return priv->accessible_role;
-    }
+  if (context != NULL && gtk_at_context_is_realized (context))
+    return gtk_at_context_get_accessible_role (context);
 
-  return gtk_at_context_get_accessible_role (context);
+  return priv->accessible_role;
 }
 
 /**